# -*- coding:utf-8 -*-

from enum import Enum
from pydantic import BaseModel
from fastapi import Depends, Request, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy import func, and_, or_

from config.database import get_db, get_model_class_by_table_name, TbAssociation
from core.exceptions import ServiceException
from utils.dependence import PageFilter

from models.user import TbUser

class BaseDao:
    def __init__(self, db: AsyncSession = Depends(get_db)):
        self.db = db
        # self.MODEL = TbUser
    async def get_by_id(self, id: int):
        result = await self.db.execute(select(self.MODEL).filter(self.MODEL.del_flag == '0', self.MODEL.id == id))
        return result.scalars().first()

    def filter(self, page_filter: PageFilter=None):
        filter = [getattr(self.MODEL, k) == v for k, v in page_filter.filter.items()] if page_filter.filter else []
        in_ = [getattr(self.MODEL, k).in_(v) for k, v in page_filter.in_.items()] if page_filter.in_ else []
        like = [getattr(self.MODEL, k).like(f'%{v}%') for k, v in page_filter.like.items()] if page_filter.like else []
        order_by = [getattr(self.MODEL, k).asc() if v else getattr(self.MODEL, k).desc() for k, v in page_filter.order_by.items()] if page_filter.order_by else []
        return filter, in_, like, order_by

    async def count(self, filter: list=[], in_: list=[], like: list=[], and_or: bool = True):
        total_count = await self.db.execute(select(func.count(getattr(self.MODEL, 'id'))).filter(
            and_(self.MODEL.del_flag == '0', 
                and_(*filter, *in_, *like) if and_or else or_(*filter, *in_, *like)
            )))
        return total_count.scalar()

    async def pages(self, page_no: int=1, page_size: int=10, page_filter: PageFilter=None, and_or: bool = True):
        filter, in_, like, order_by= self.filter(page_filter)
        total_count = await self.count(filter, in_, like, and_or)
        stm = select(self.MODEL).filter(self.MODEL.del_flag == '0', *filter, *in_, *like)
        if page_size == -1:
            page_size = total_count
            page_no = 1
        result = await self.db.execute(stm.order_by(*order_by).offset((page_no - 1) * page_size).limit(page_size))
        # 计算总页数
        total_pages = (total_count // page_size) + (1 if total_count % page_size > 0 else 0)
        return {
            "pageNo": page_no if page_size != -1 else 1,
            "pageSize": page_size if page_size != -1 else total_count,
            "totalPage": total_pages,
            "totalRows": total_count,
            "rows": result.scalars().all(),
        }

    async def first(self, obj: BaseModel):
        filter = [getattr(self.MODEL, f'{column.name}') == getattr(obj, f'{column.name}') for column in self.MODEL.__table__.columns if hasattr(obj, f'{column.name}') and getattr(obj, f'{column.name}') is not None]
        result = await self.db.execute(select(self.MODEL).filter(self.MODEL.del_flag == '0', *filter))
        return result.scalars().first()
    
    async def add(self, obj: BaseModel, commit: bool=False, flush: bool = True):
        data = {key: val.value if isinstance(val, Enum) else val for key, val in obj.dict().items()}
        target = self.MODEL(**data)
        self.db.add(target)
        if commit:
            await self.db.commit()
        elif flush:
            await self.db.flush()
        return target

    async def get_ref(self, ref_ids: list[int], ref_table_name: str, ref_type: str):
        ref_table = get_model_class_by_table_name(ref_table_name)
        if ref_table is None:
            raise Exception(f"Table {ref_table_name} not found")
        stm = select(TbAssociation.source_id, ref_table).join(ref_table, and_(ref_table.id == TbAssociation.target_id)).where(TbAssociation.del_flag == '0', TbAssociation.association_type == ref_type, TbAssociation.source_id.in_(ref_ids))
        result = await self.db.execute(stm)
        result = result.all()

        return {id: [item[1] for item in result if item[0] == id] for id in ref_ids}

    async def add_ref(self, id: int, ref_ids: list[int], ref_table_name: str, ref_type: str, create_user: int = -1, commit: bool=False, flush: bool = True):
        ref_table = get_model_class_by_table_name(ref_table_name)
        for ref_id in ref_ids:
            self.db.add(TbAssociation(source_id=id, target_id=ref_id, association_type=ref_type, create_user=create_user))
        if commit:
            await self.db.commit()
        elif flush:
            await self.db.flush()

    async def delete_ref(self, id: int, ref_ids: list[int], ref_table_name: str, ref_type: str, delete_user: int = -1, commit: bool=False, flush: bool = True):
        ref_table = get_model_class_by_table_name(ref_table_name)
        for ref_id in ref_ids:
            db_data = await self.db.execute(select(TbAssociation).filter(TbAssociation.del_flag == '0', TbAssociation.source_id == id, TbAssociation.target_id == ref_id, TbAssociation.association_type == ref_type))
            db_data = db_data.scalars().first()
            if db_data:
                db_data.del_flag = '1'
                db_data.update_user = delete_user
        if commit:
            await self.db.commit()
        elif flush:
            await self.db.flush()

    async def delete(self, id: int, commit: bool=False, flush: bool = True):
        target = await self.get_by_id(id)
        target.del_flag = 1
        if commit:
            await self.db.commit()
        elif flush:
            await self.db.flush()
        return target
    
    async def update(self, id: int, data: dict={}, commit: bool=False, flush: bool = True):
        target = await self.get_by_id(id)
        for key, val in data.items():
            if val is None or key not in self.MODEL.__table__.columns: continue
            setattr(target, key, val)
        if commit:
            await self.db.commit()
        elif flush:
            await self.db.flush()
        return target

